﻿//-----------------------------------------------------------------------
// <copyright file="LiveTileHelper.cs" company="Steve Syrell">
//     Copyright 2012 Steve Syrell
// </copyright>
//-----------------------------------------------------------------------

namespace SteveSyrell.Tools.Phone.LiveTiles
{
    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.IO;
    using System.IO.IsolatedStorage;
    using System.Linq;
    using System.Runtime.Serialization;
    using Shell = Microsoft.Phone.Shell;

    /// <summary>
    /// Windows Phone live tile helpers
    /// </summary>
    public class LiveTileHelper
    {
        /// <summary>
        /// The folder location where tile images are stored.
        /// </summary>
        internal const string TileImageLocation = @"Shared\ShellContent";

        /// <summary>
        /// The path of the file containing the serialized list of active live tiles.
        /// </summary>
        private const string ActiveTileListFile = @"SteveSyrell.Tools.Phone.LiveTileHelper.ActiveLiveTileList.xml";

        /// <summary>
        /// An object to lock on.
        /// </summary>
        private static object lockObject;

        /// <summary>
        /// Initializes static members of the LiveTileHelper class.
        /// </summary>
        static LiveTileHelper()
        {
            LiveTileHelper.lockObject = new object();
        }

#if !BACKGROUND_AGENT
        /// <summary>
        /// Creates and pins a new tile to the start screen.
        /// </summary>
        /// <param name="navigationUri">The URI for the tile to create.</param>
        /// <param name="tileData">The data to use to create the file tile.</param>
        public static void CreateTile(Uri navigationUri, TileData tileData)
        {
            // Lock to make sure that there are not concurrency issues between the background and foreground threads.
            lock (LiveTileHelper.lockObject)
            {
                // Ensure proper tile data is passed in.
                if (tileData == null)
                {
                    throw new ArgumentNullException("tileData");
                }

                // Cleanup unused tile resources.
                LiveTileHelper.CleanupTileResources();

                // Create system tile data from the provided tile data.
                Shell.ShellTileData systemTileData = tileData.GetSystemShellTileData(true);

                // Add this new tile to the list of active tiles.
                tileData.NavigationUri = navigationUri;
                List<TileData> activeTileList = LiveTileHelper.GetActiveTileList();
                activeTileList.Add(tileData);
                LiveTileHelper.SaveActiveTileList(activeTileList);

                // Pin the tile to start.
                Shell.ShellTile.Create(navigationUri, systemTileData, tileData.SupportsWideTile);
            }
        }
#endif

        /// <summary>
        /// Updates the specified tile with new tile data.
        /// </summary>
        /// <param name="tileToUpdate">The tile to update.</param>
        /// <param name="newTileData">The new tile data.</param>
        public static void UpdateTile(Shell.ShellTile tileToUpdate, TileData newTileData)
        {
            // Lock to make sure that there are not concurrency issues between the background and foreground threads.
            lock (LiveTileHelper.lockObject)
            {
                // Create system tile from the provided tile data
                Shell.ShellTileData newShellTileData = newTileData.GetSystemShellTileData(false);

                // Update the tile.
                tileToUpdate.Update(newShellTileData);

                // Remove the old tile resources.
                List<TileData> activeTileList = LiveTileHelper.GetActiveTileList();
                TileData oldTileData = activeTileList.Where(tile => tile.NavigationUri.Equals(tileToUpdate.NavigationUri)).FirstOrDefault();
                if (oldTileData != null)
                {
                    newTileData.CopyNonUpdatedData(oldTileData);
                    oldTileData.CleanupResources();
                    activeTileList.Remove(oldTileData);
                }

                // Add the new tile to the list of active tiles.
                newTileData.NavigationUri = tileToUpdate.NavigationUri;
                activeTileList.Add(newTileData);

                // Save the updated tile list.
                LiveTileHelper.SaveActiveTileList(activeTileList);

#if !BACKGROUND_AGENT
                // Cleanup unused tile resources.
                LiveTileHelper.CleanupTileResources();
#endif
            }
        }

        /// <summary>
        /// Deletes the specified tile.
        /// </summary>
        /// <param name="tileToDelete">The tile to delete.</param>
        public static void DeleteTile(Shell.ShellTile tileToDelete)
        {
            // Lock to make sure that there are not concurrency issues between the background and foreground threads.
            lock (LiveTileHelper.lockObject)
            {
                // Un-pin the tile.
                tileToDelete.Delete();

                // Delete the images used for the tile and remove it from the active tile list.
                List<TileData> activeTileList = LiveTileHelper.GetActiveTileList();
                TileData deletedTileData = activeTileList.Where(tile => tile.NavigationUri.Equals(tileToDelete.NavigationUri)).FirstOrDefault();
                if (deletedTileData != null)
                {
                    deletedTileData.CleanupResources();
                    activeTileList.Remove(deletedTileData);
                }

                // Save the updated tile list.
                LiveTileHelper.SaveActiveTileList(activeTileList);

#if !BACKGROUND_AGENT
                // Cleanup unused tile resources.
                LiveTileHelper.CleanupTileResources();
#endif
            }
        }

        /// <summary>
        /// Cleans up any unused tile resources.
        /// </summary>
        private static void CleanupTileResources()
        {
            // The list of images that are actively being used by tiles.
            List<string> imagesInUse = new List<string>();

            // Get the URIs of the active system tiles.
            List<Uri> systemActiveTileUriList = new List<Uri>();
            foreach (Shell.ShellTile tile in Shell.ShellTile.ActiveTiles)
            {
                systemActiveTileUriList.Add(tile.NavigationUri);
            }

            // Get the list of last-known active tiles
            List<TileData> activeTileList = LiveTileHelper.GetActiveTileList();

            // Now select all tiles whose URIs are not in the current system list.
            List<TileData> staleTiles = new List<TileData>(activeTileList.Where(tile => !systemActiveTileUriList.Contains(tile.NavigationUri)));

            // Cleanup each of the stale tiles and remove them from the helper's list of active tiles
            foreach (TileData staleTile in staleTiles)
            {
                staleTile.CleanupResources();
                activeTileList.Remove(staleTile);
            }

            // Save the updated tile list.
            LiveTileHelper.SaveActiveTileList(activeTileList);

            // As a last resort, if the active tile list gets out of sync, 
            // we should cleanup any orphaned images.
            try
            {
                using (var store = IsolatedStorageFile.GetUserStoreForApplication())
                {
                    // Get the list of active tile images.
                    foreach (TileData tile in activeTileList)
                    {
                        imagesInUse.AddRange(tile.GetImageFileNames());
                    }

                    // Get the list of all jpeg files in the tile image directory.
                    string[] allTileImages = store.GetFileNames(LiveTileHelper.TileImageLocation + "\\*.jpg");

                    // Get all tile images that are not in use by an active tile
                    List<string> orphanedTileImages = new List<string>(allTileImages.Where(tileImage => !imagesInUse.Contains(tileImage)));

                    // Delete each orphaned tile image.
                    foreach (string orphanedTileImage in orphanedTileImages)
                    {
                        store.DeleteFile(Path.Combine(LiveTileHelper.TileImageLocation, orphanedTileImage));
                    }
                }
            }
            catch (IsolatedStorageException e)
            {
                // TODO, write an error to flurry.
                Debug.WriteLine("Failure during orphaned tile-image cleanup. Callstack: {0}", e.StackTrace);
            }
        }

        /// <summary>
        /// Gets the list of active tiles.
        /// </summary>
        /// <returns>The list of active tiles.</returns>
        private static List<TileData> GetActiveTileList()
        {
            using (IsolatedStorageFile store = IsolatedStorageFile.GetUserStoreForApplication())
            {
                if (store.FileExists(LiveTileHelper.ActiveTileListFile))
                {
                    using (IsolatedStorageFileStream fileStream = store.OpenFile(LiveTileHelper.ActiveTileListFile, System.IO.FileMode.Open))
                    {
                        DataContractSerializer serializer = new DataContractSerializer(typeof(List<TileData>), new Type[] { typeof(FlipTileData) });
                        return (List<TileData>)serializer.ReadObject(fileStream);
                    }
                }
            } 

            // If we get here, the file did not exist, so we have no active tiles. Therefore, just return an empty list.
            return new List<TileData>();
        }

        /// <summary>
        /// Saves the list of active tiles to isolated storage.
        /// </summary>
        /// <param name="tileDataToSave">The list of active tiles to save.</param>
        private static void SaveActiveTileList(List<TileData> tileDataToSave)
        {
            using (IsolatedStorageFile store = IsolatedStorageFile.GetUserStoreForApplication())
            {
                // If an old list file already exists, this will overwrite it.
                using (IsolatedStorageFileStream fileStream = store.OpenFile(LiveTileHelper.ActiveTileListFile, System.IO.FileMode.Create))
                {
                    DataContractSerializer serializer = new DataContractSerializer(typeof(List<TileData>), new Type[] { typeof(FlipTileData), typeof(TileData) });
                    serializer.WriteObject(fileStream, tileDataToSave);
                }
            }
        }
    }
}
